iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0

第 9 天把一個連結走到文章的過程拆成四次交接。到了第二次交接,我只敢把輸出叫作「文字候選」。

因為一份逐字稿就算排版整齊、讀起來通順,還是可能聽錯、漏字、重複,或在整理時失去原本的時間資訊。它可以進入下一站;要直接引用,還欠一次回查。

今天就把「擷取產物 → 文字候選」這一格放大,只回答一個問題:

一段準備被摘要或引用的整理後文字,怎麼回到產生它的字幕片段與影音時間窗?

模型比較和通用及格分數先放在一旁。說話者是誰,留到 Day 11;影片裡的說法有沒有外部證據,留到 Day 13。今天先守住最前面這段路:文字整理過後,來源座標還在不在。

文字變乾淨時,回去的路也可能一起被擦掉

人讀逐字稿,希望它越乾淨越好。程式要回查來源,需求剛好相反。它需要片段識別、開始時間、結束時間、字幕版本,以及這段文字經過哪些整理。

我公開的媒體轉文字工具,剛好可以看到這個落差。以下只看固定版本的程式碼,不把它寫成這次案例實際使用的工具。

原始 JSON 那一層,每個片段原本有 idstartendtext;使用 faster-whisper 的路徑,還可能留下字詞的起訖時間與機率值。可是輸出成方便閱讀的 transcript.md 後,只留下片段起點和文字;到了 transcript.txt,連時間也沒有了。

後面的輸出流程 還會用 OpenCC 整理全文與片段文字,再覆寫名為 whisper_raw.json 的檔案,卻沒有同步改寫 words[].word,也沒有另外記下轉換步驟。檔名雖然還有 raw,內容已經不是模型剛輸出的原始位元。

工具照樣能用。這個落差提醒我:適合閱讀的逐字稿,和適合回放的逐字稿,是兩種不同的產物。

前者要減少雜訊;後者要保留來路。內容產線如果只存前者,後面的 AI 確實比較好讀,卻很難回答某一句到底從哪裡來。

三個問題,不能共用一盞綠燈

要談回放,我先把三種判定拆開:

判定 它回答的問題 這次能走到哪裡
位置/location_status 給定時間碼,能不能落在這份字幕的片段範圍,形成播放定位? 可以做確定性檢查,但只限字幕範圍
文字忠實/fidelity_status 整理後文字是否忠於該時間窗的原聲? unchecked,本輪沒有人工聽校
來源主張/truth_status 影片作者說的內容是否符合外部事實? out-of-scope,留到 Day 13

位置找得到,不等於文字聽得對。文字聽得對,也不等於影片裡的主張是真的。

這三件事一旦塞進同一個 verified: true,下游看到的只會是一盞很有自信、卻不知道驗過什麼的綠燈。

這次實驗還有兩個來源關係要另外記:caption_lineage_status: pass,表示整理後文字已沿片段序號綁回指定字幕;same_version_media_replay_status: unknown,表示缺少影音檔摘要,同一版本的原聲仍未鎖定。這兩項各自保留,不併進前面三項。

先鎖定這次到底讀了哪些東西

這次準備文章時,做了一個很窄的回放實驗。輸入只有三份:

  1. 一份 VTT 字幕
  2. 一份帶時間碼的來源台帳
  3. 一份相鄰的影片中繼資料

執行前後都用 SHA-256 計算摘要,確認正式輸入沒有被實驗改動。這裡的摘要只做一件事:辨認「這次讀的是不是同一份位元」。

摘要相同,能說的只有位元相同。字幕由誰產生、文字有沒有聽對,以及影片作者說的是否成立,都需要別的證據。NIST FIPS 180-4 定義的是訊息摘要,不是來源簽章。SHA-256 的責任到這裡就結束了。

這份來源包還缺一樣關鍵資料:影音檔本身的摘要。平台字幕軌 ID、字幕擷取時間與擷取工具版本也沒有留下。YouTube 的字幕資料模型 會替字幕軌保存獨立 ID、語言、種類與更新資訊,這也說明只留影片 ID 還不夠。因此我能說的是「這些片段序號屬於這一份字幕」;至於是否回到完全相同版本的影音,狀態維持 unknown

這個差別很小,卻會影響後面每一句話可以寫多滿。

看起來全綠的結果,其實只驗到範圍

第一版檢查很直白:讀出台帳裡的每一個時間碼,看它是否落在某個字幕片段的開始與結束之間。

結果很好看。這批台帳時間碼在範圍檢查裡沒有報錯。

如果在這裡停下來,很容易把結果寫成「每一筆內容都能回到原片」。實際上,這個檢查只問了:

這個秒數是否存在於字幕時間軸?

它沒有問:

台帳裡這段文字,是否真的對應到那個秒數附近的內容?

為了確認這個盲點,正式台帳沒有被改動;實驗只在系統暫存副本裡,把其中一個時間碼換成片內另一個語意無關的時間碼。

新的時間仍在字幕涵蓋範圍內,所以舊檢查照樣沒有報錯。

這是一個設計好的限制探針,不是本次案例真的找出一筆錯誤。它證明的是:時間碼格式正確、也落在字幕裡,仍然可能指向不相干的位置。

所以 location_status: pass 只能表示定位可解析,不能代替語意對應,更不能把 fidelity_status 一起改成 confirmed

一個時間碼,不足以讓文字回得去

要讓整理後文字可以回放,我需要保存的不是一個孤立的時間碼,而是一條完整關係:

整理後文字
  → 整理前文字
  → 字幕片段序號
  → 同一份字幕摘要
  → 起訖時間窗
  → 公開播放位置

這條關係裡,每一段都有工作。

單獨的時間碼無法指認字幕版本。字幕片段序號也得有依附對象,因為 WebVTT 的片段識別碼可以省略;這次讀到的 VTT 就沒有明示識別碼,只能用檔案中的順序建立 cue_ordinals

順序本身不是永久身分。字幕只要重新分段、插入一個片段,後面的序號就可能全部改變。因此 cue_ordinals 必須和 caption_artifact_sha256 一起保存。摘要一變,舊序號就過期,重新建立關係後才能使用。

WebVTT 規格 還允許字幕片段在時間上重疊。也就是說,一個時間點可能找不到候選、找到一個,或同時落在多個片段。程式若只取第一個命中,這是實作選擇,不是格式保證的唯一答案。

時間窗本身也有邊界。我採用半開區間來判定:

start <= point < end

這和 W3C Media Fragments 描述媒體時間片段的方式一致。不過時間碼仍只是導航。播放器或影音工具可能為了從可解碼位置開始,落在稍早的位置;精準度停在「縮小回聽範圍」,還沒到逐幀或逐音框對齊。

Replayable Transcript Packet v1

把前面的需求收斂後,我整理出一份最小的「可回放逐字稿包」。這是本文的提案,也是這次文章實驗新建立的資料形狀;現有證據只到這裡,既有影片導讀流程尚未被證明共用這套格式。

{
  "source": {
    "source_id": "<公開來源識別碼>",
    "canonical_locator": "<公開網址>",
    "source_media_sha256": null
  },
  "caption_artifact": {
    "sha256": "<字幕摘要>",
    "track_id": null,
    "language": "<字幕語言>",
    "kind": "<字幕軌種類或 unknown>",
    "captured_at": null,
    "capture_tool_version": null
  },
  "segment": {
    "segment_id": "<此資料包內的穩定值>",
    "cue_ordinals": ["<cue-ordinal>"],
    "start_ms": "<start-ms>",
    "end_ms": "<end-ms>",
    "raw_text": "<保留於工作產物>",
    "normalized_text": "<保留於工作產物>",
    "normalization_ops": [
      "strip-webvtt-inline-timestamps",
      "strip-webvtt-tags",
      "collapse-whitespace",
      "remove-rolling-caption-token-overlap"
    ],
    "caption_lineage_status": "pass",
    "location_status": "pass | fail",
    "same_version_media_replay_status": "unknown",
    "fidelity_status": "unchecked | confirmed | contradicted | unclear",
    "truth_status": "out-of-scope"
  }
}

這裡刻意同時保留 raw_textnormalized_textnormalization_ops

移除 WebVTT 標記、合併空白,通常不難理解。比較麻煩的是滾動字幕:後一個片段常會重複前一個片段的尾巴。如果只把每段文字串起來,同一句話可能出現兩次;如果去重規則太積極,又可能刪掉原本真的重複說過的字。

因此整理後文字應該另存,原文繼續保留。新文字還要記下自己由哪一份原文、經過哪些操作產生。這也是 W3C PROV-DM 對內容產線很有用的地方:輸入、轉換活動與輸出要分開描述。檔案放在同一個資料夾只能證明它們一起出現,逐段衍生關係仍要另外記錄。

四個擋得住的錯誤,和一個擋不住的錯誤

資料形狀寫完,還要知道它失敗時會不會真的停。

這次在暫存副本與記憶體裡做了四個負向測試,正式字幕和台帳都沒有被改動:

刻意破壞的地方 稽核器應該回報
改動字幕的一處位元 caption-digest-mismatch
end_ms 小於 start_ms invalid-time-range
指向不存在的字幕片段序號 cue-reference-missing
保留內容,但替換字幕軌的語言身分 caption-track-mismatch

四項都照預期失敗。這代表同一份字幕、有效時間範圍、存在的片段關係與字幕軌身分,可以被程式擋住。

但前面那個「片內時間換成另一個片內時間」的限制探針沒有被擋。因為兩個時間都合法,也都落在字幕範圍裡。要知道文字是否真的對到那段原聲,還是得回到內容本身。

所以 Day 10 不該再寫成一篇「多加幾個驗證器就安全了」。機器很適合檢查位元、欄位、範圍與關係;語意是否吻合,沒有人工聽校或可信的對齊證據時,就應該保留 unchecked

接回內容產線:什麼時候可以引用?

回放資料最後要影響工作流下一步,不然它只是一份漂亮的 JSON。

下面先把控制方式整理成一份提案:

檢查結果 下一步 文章能寫到哪裡
字幕摘要不符、片段不存在或時間範圍無效 reject 不使用舊片段證據
來源影音版本無法核對 needs-human 只說找到公開定位,不說已回到同一版影音
fidelity_statusuncheckedunclear needs-human 不下精確引言,先回聽
人工聽校結果是 contradicted reject 或重整文字 留下錯誤紀錄,不引用整理句
人工聽校結果是 confirmed,外部真實性未查 只做來源歸屬 寫成「影片作者表示」,不升級成外部事實

這份表目前停在文章提案,現有證據沒有支持它已接進所有內容工作流。

它和 AI 自動化的關係也很直接:AI 可以繼續摘要、分類、找觀點,但它拿到的每一段文字都要帶著目前能相信到哪裡。位置失效就拒收;文字還沒聽校就停在人這裡;已聽對但尚未查證,就保留來源歸屬。

自動化在這裡的價值,是讓不同的不確定走到不同的下一步。

今天留下的不是更漂亮的逐字稿

第 9 天問的是:下一站憑什麼接手?

Day 10 的答案,是把乾淨文字和回程資料一起交出去:字幕版本、片段關係、起訖時間、整理操作,以及彼此分開的判定狀態。

這樣做的作用很務實:錯誤發生時,還有路可以回去;證據不足時,也知道要在哪裡停下來。至於逐字稿是否正確,仍由回聽結果回答。

今天先找到「這句話位於哪裡」。即使下一步真的回聽並確認內容,多人會議或訪談還會多一個問題:

這句到底是誰說的?

Day 11 再來處理說話者標記、姓名與人工確認之間,最容易被 AI 填得太滿的那一段。

參考資料


上一篇
Day 9|一個連結不會自己變成文章:影片導讀產線揭露
下一篇
Day 11|時間窗找到了,怎麼確認「這是誰說的」?
系列文
情報進來,內容發出去:30 天拆一條每天真的在跑的 AI 內容產線12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言